package com.kalvan.sensitive.utils;

import com.kalvan.sensitive.annotation.Desensitized;
import com.kalvan.sensitive.enums.SensitiveType;
import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author chenliang <br>
 * 敏感信息屏蔽工具<br>
 */
public final class SensitiveInfoUtils {

    public static void main(String[] args) {
        System.out.println(chineseName("陈楚留香"));
        System.out.println(idCardNum("43099112166677"));
        System.out.println(fixedPhone("0755-6888888"));
        System.out.println(mobilePhone("18938655105"));
        System.out.println(email("chenliang@eptok.com"));
        System.out.println(bankCard("62287776823182192919"));
        System.out.println(bankName("中国工商银行民治支行"));
        System.out.println(address("中国广东省深圳市龙华民治街道玉龙路997号远景大厦"));
    }

    private static final String NULLSTR = "null";

    /**
     * [中文姓名] 只显示第一个汉字，其他隐藏为星号<例子：李**>
     *
     * @param fullName
     * @return
     */
    public static String chineseName(String fullName) {
        if (StringUtils.isBlank(fullName)) {
            return fullName;
        }
        String name = StringUtils.left(fullName, 1);
        return StringUtils.rightPad(name, 2, "*");
    }

    /**
     * [证件号码类]（身份证，军官证，护照等身份证明证件类） 后4位用****（6个*）代替。不足7位，直接******（6个*）代替
     *
     * @param id
     * @return
     */
    public static String idCardNum(String id) {
        if (StringUtils.isBlank(id)) {
            return id;
        }
        if (id.length() > 7) {
            return StringUtils.left(id, 3) + "******" + StringUtils.right(id, 4);
        }
        return "******";
    }

    /**
     * [固定电话] 后四位，其他隐藏<例子：****1234>
     *
     * @param phone
     * @return
     */
    public static String fixedPhone(String phone) {
        if (StringUtils.isBlank(phone)) {
            return phone;
        }
        if (phone.length() > 4) {
            return StringUtils.leftPad(StringUtils.right(phone, 4), StringUtils.length(phone), "*");
        }
        return "******";
    }

    /**
     * [手机号码类]（联系人电话，个人手机） 前3位显示，后4位显示，中间部分******（6个*）代替 不足7位直接******（6个*）代替
     *
     * @param phone
     * @return
     */
    public static String mobilePhone(String phone) {
        if (StringUtils.isBlank(phone)) {
            return phone;
        }
        if (phone.length() > 7) {
            return StringUtils.left(phone, 3) + "******" + (StringUtils.right(phone, 4));
        }
        return "******";
    }

    /**
     * 【地址】只显示到地区，不显示详细地址，比如：北京市海淀区****
     *
     * @param address
     * @param sensitiveSize 敏感信息长度
     * @return
     */
    public static String address(String address, int sensitiveSize) {
        if (StringUtils.isBlank(address)) {
            return "";
        }
        int length = StringUtils.length(address);
        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    }

    /**
     * [地址类] 前2位显示 ，后2位显示，中间部分******（6个*）代替 不足4位全部******（6个*）代替
     *
     * @param address
     * @return
     */
    public static String address(String address) {
        if (StringUtils.isBlank(address)) {
            return address;
        }
        if (address.length() > 4) {
            return StringUtils.left(address, 2) + "******" + StringUtils.right(address, 2);
        }
        return "******";
    }

    /**
     * [邮箱类]******.com / (.)号之前全部******（6个*）代替，（.）号之后显示出来
     *
     * @param email
     * @return
     */
    public static String email(String email) {
        if (StringUtils.isBlank(email)) {
            return email;
        }
        int index = email.lastIndexOf(".");
        if (index > 1) {
            return "******" + StringUtils.right(email, email.length() - index);
        }
        return "******";
    }

    /**
     * [卡号 ] 前2位显示，后4位显示，中间部分******（6个*）代替
     *
     * @param cardNum
     * @return
     */
    public static String bankCard(String cardNum) {
        if (StringUtils.isBlank(cardNum)) {
            return cardNum;
        }
        if (cardNum.length() > 6) {
            return StringUtils.left(cardNum, 2) + "******" + StringUtils.right(cardNum, 4);
        }
        return "******";
    }

    /**
     * [银行名] 显示前4位
     *
     * @param bankName
     * @return
     */
    public static String bankName(String bankName) {
        if (StringUtils.isBlank(bankName)) {
            return bankName;
        }
        if (bankName.length() > 4) {
            return StringUtils.rightPad(StringUtils.left(bankName, 4), StringUtils.length(bankName), "*");
        }
        return StringUtils.rightPad(StringUtils.left(bankName, 1), StringUtils.length(bankName), "*");
    }

    /**
     * [统一社会信用代码类（注册码，营业执照，牌照类的码） ] 前4位显示，后4位显示，中间部分******（6个*）代替
     *
     * @param code
     * @return
     */
    public static String cnapsCode(String code) {
        if (StringUtils.isBlank(code)) {
            return code;
        }
        if (code.length() > 8) {
            return StringUtils.left(code, 4) + "******" + StringUtils.right(code, 4);
        }
        return "******";
    }

    /**
     * [银行卡有效期] 前1位，后1位，其他隐藏<例子:“0**6”>
     *
     * @param date
     * @return
     */
    public static String cardValidDate(String date) {
        if (StringUtils.isBlank(date)) {
            return date;
        }
        return StringUtils.left(date, 1).concat(StringUtils
                .removeStart(StringUtils.leftPad(StringUtils.right(date, 1), StringUtils.length(date), "*"), "*"));
    }

    // /**
    // * 【密码】密码的全部字符都用*代替，比如：******
    // *
    // * @param password
    // * @return
    // */
    // public static String password(String password) {
    // if (StringUtils.isBlank(password)) {
    // return "";
    // }
    // String pwd = StringUtils.left(password, 0);
    // return StringUtils.rightPad(pwd, StringUtils.length(password), "*");
    // }

    /**
     * 全部隐藏
     *
     * @param data
     * @param sensitiveSize
     * @return
     */
    public static String all(String data, int sensitiveSize) {
        return StringUtils.repeat("*", sensitiveSize);
    }

    /**
     * 根据信息类型屏蔽<br>
     *
     * @param type  信息类型
     * @param value 信息value 如 62121212312312
     * @return
     */
    public static String convertMsg(SensitiveType type, String value) {
        if (StringUtils.isBlank(value)) {
            return "";
        }
        // 返回null 时保持原样返回方便查问题
        if (NULLSTR.equalsIgnoreCase(value)) {
            return value;
        }
        switch (type) {
            case CHINESE_NAME: {
                value = SensitiveInfoUtils.chineseName(value);
                break;
            }
            case ID_CARD: {
                value = SensitiveInfoUtils.idCardNum(value);
                break;
            }
            case FIXED_PHONE: {
                value = SensitiveInfoUtils.fixedPhone(value);
                break;
            }
            case MOBILE_PHONE: {
                value = SensitiveInfoUtils.mobilePhone(value);
                break;
            }
            case ADDRESS: {
                value = SensitiveInfoUtils.address(value);
                break;
            }
            case EMAIL: {
                value = SensitiveInfoUtils.email(value);
                break;
            }
            case BANK_CARD: {
                value = SensitiveInfoUtils.bankCard(value);
                break;
            }
            case BANK_NAME: {
                value = SensitiveInfoUtils.bankName(value);
                break;
            }
            case CNAPS_CODE: {
                value = SensitiveInfoUtils.cnapsCode(value);
                break;
            }
            case BANK_CARD_DATE: {
                value = SensitiveInfoUtils.cardValidDate(value);
                break;
            }
            case PASSWORD: {
                value = SensitiveInfoUtils.all(value, 6);
                break;
            }
            case ALL: {
                value = SensitiveInfoUtils.all(value, 3);
                break;
            }
            case NULL: {
                value = "";
                break;
            }
            default:
                break;
        }
        return value;
    }

    /**
     * 根据对象注解 @Desensitized 处理敏感信息</br>
     * 支持集合/<br>
     *
     * @param javaBean
     * @throws Exception
     * @see
     */
    public static Object processObject(Object javaBean) {
        if (javaBean == null) {
            return javaBean;
        }
        if (javaBean.getClass().isInterface()) {
            return javaBean;
        }
        /* 定义一个计数器，用于避免重复循环自定义对象类型的字段 */
        Set<Integer> referenceCounter = new HashSet<Integer>();
        /* 对克隆实体进行脱敏操作 */
        replace(getAllFields(javaBean), javaBean, referenceCounter);
        /* 清空计数器 */
        referenceCounter.clear();
        referenceCounter = null;
        return javaBean;
    }

    /**
     * 根据对象注解 @Desensitized 处理敏感信息</br>
     *
     * @param javaBean
     * @param hasPlaintextPermission 有明文权限不进行脱敏
     * @return
     */
    public static Object processObject(Object javaBean, boolean hasPlaintextPermission) {
        if (hasPlaintextPermission) {
            return javaBean;
        }
        return processObject(javaBean);
    }

    /**
     * 对需要脱敏的字段进行转化
     *
     * @param fields
     * @param javaBean
     * @param referenceCounter
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    private static void replace(Field[] fields, Object javaBean, Set<Integer> referenceCounter)
            throws RuntimeException {
        if (null != fields && fields.length > 0) {
            for (Field field : fields) {
                field.setAccessible(true);
                if (null != field && null != javaBean) {
                    Object value = null;
                    try {
                        value = field.get(javaBean);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                    if (null != value) {
                        Class<?> type = value.getClass();
                        // 处理子属性，包括集合中的
                        if (type.isArray()) {// 对数组类型的字段进行递归过滤
                            int len = Array.getLength(value);
                            for (int i = 0; i < len; i++) {
                                Object arrayObject = Array.get(value, i);
                                if (arrayObject == null) {
                                    continue;
                                }
                                if (isNotGeneralType(arrayObject.getClass(), arrayObject, referenceCounter)) {
                                    replace(getAllFields(arrayObject), arrayObject, referenceCounter);
                                }
                            }
                        } else if (value instanceof Collection<?>) {// 对集合类型的字段进行递归过滤
                            Collection<?> c = (Collection<?>) value;
                            Iterator<?> it = c.iterator();
                            while (it.hasNext()) {
                                // TODO: 待优化
                                Object collectionObj = it.next();
                                if (collectionObj == null) {
                                    continue;
                                }
                                if (isNotGeneralType(collectionObj.getClass(), collectionObj, referenceCounter)) {
                                    replace(getAllFields(collectionObj), collectionObj, referenceCounter);
                                }
                            }
                        } else if (value instanceof Map<?, ?>) {// 对Map类型的字段进行递归过滤
                            Map<?, ?> m = (Map<?, ?>) value;
                            Set<?> set = m.entrySet();
                            if (set == null) {
                                continue;
                            }
                            for (Object o : set) {
                                Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
                                Object mapVal = entry.getValue();
                                if (mapVal == null) {
                                    continue;
                                }
                                if (isNotGeneralType(mapVal.getClass(), mapVal, referenceCounter)) {
                                    replace(getAllFields(mapVal), mapVal, referenceCounter);
                                }
                            }
                        } else if (value instanceof Enum<?>) {
                            continue;
                        }

                        /* 除基础类型、jdk类型的字段之外，对其他类型的字段进行递归过滤 */
                        else {
                            if (!type.isPrimitive() && type.getPackage() != null
                                    && !StringUtils.startsWith(type.getPackage().getName(), "javax.")
                                    && !StringUtils.startsWith(type.getPackage().getName(), "java.")
                                    && !StringUtils.startsWith(field.getType().getName(), "javax.")
                                    && !StringUtils.startsWith(field.getName(), "java.")
                                    && referenceCounter.add(value.hashCode())) {
                                replace(getAllFields(value), value, referenceCounter);
                            }
                        }
                    }
                    // 脱敏操作
                    setNewValueForField(javaBean, field, value);

                }
            }
        }
    }

    /**
     * 脱敏操作（按照规则转化需要脱敏的字段并设置新值） 目前只支持String类型的字段，如需要其他类型如BigDecimal、Date等类型，可以添加
     *
     * @param javaBean
     * @param field
     * @param value
     * @throws IllegalAccessException
     */
    private static void setNewValueForField(Object javaBean, Field field, Object value) throws RuntimeException {
        // 处理自身的属性
        Desensitized annotation = field.getAnnotation(Desensitized.class);
        if (field.getType().equals(String.class) && null != annotation
                && executeIsEffictiveMethod(javaBean, annotation)) {
            String valueStr = (String) value;
            if (StringUtils.isNotBlank(valueStr)) {
                try {
                    field.set(javaBean, SensitiveInfoUtils.convertMsg(annotation.type(), valueStr));
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 执行某个对象中指定的方法
     *
     * @param javaBean     对象
     * @param desensitized
     * @return
     */
    private static boolean executeIsEffictiveMethod(Object javaBean, Desensitized desensitized) {
        boolean isAnnotationEffictive = true;// 注解默认生效
        if (desensitized != null) {
            String isEffictiveMethod = desensitized.isEffictiveMethod();
            if (StringUtils.isNotBlank(isEffictiveMethod)) {
                try {
                    Method method = javaBean.getClass().getMethod(isEffictiveMethod);
                    method.setAccessible(true);
                    isAnnotationEffictive = (Boolean) method.invoke(javaBean);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
        return isAnnotationEffictive;
    }

    /**
     * 排除基础类型、jdk类型、枚举类型的字段
     *
     * @param clazz
     * @param value
     * @param referenceCounter
     * @return
     */
    private static boolean isNotGeneralType(Class<?> clazz, Object value, Set<Integer> referenceCounter) {
        return !clazz.isPrimitive() && clazz.getPackage() != null && !clazz.isEnum()
                && !StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
                && !StringUtils.startsWith(clazz.getPackage().getName(), "java.")
                && !StringUtils.startsWith(clazz.getName(), "javax.")
                && !StringUtils.startsWith(clazz.getName(), "java.") && referenceCounter.add(value.hashCode());
    }

    /**
     * 获取包括父类所有的属性
     *
     * @param objSource
     * @return
     */
    private static Field[] getAllFields(Object objSource) {
        /* 获得当前类的所有属性(private、protected、public) */
        List<Field> fieldList = new ArrayList<Field>();
        Class<?> tempClass = objSource.getClass();
        while (tempClass != null && !tempClass.getName().toLowerCase().equals("java.lang.object")) {// 当父类为null的时候说明到达了最上层的父类(Object类).
            fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            tempClass = tempClass.getSuperclass(); // 得到父类,然后赋给自己
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }
}
